在現代前端框架盛行的年代,很少會需要大量的手動設置 <link>,基本上 bundler 會處理好各種 JavaScript, CSS 的載入,只有少部分會需要在 index.html 設定;也因此顯少有機會深入研究 <link> 的各種 attribute。
為何會在 HTTP 的系列文章提到 <link> 呢?因為 HTTP response header 也可以設定 Link!所以就趁這篇文章,也順便把 HTML 的 <link> 也介紹一遍吧~
由於 iThome 使用 cloudflare,發文若有一些 XSS 的關鍵字會被擋下,所以本文若有用到 script 標籤,都會改成 <xcript>
稍後在介紹 <link> 時,會提到這個概念

<link rel>rel = relationship,這是 <link> 身上很重要的 attribute,接下來會介紹幾個比較常用的值
<link rel="alternate">主要是為了 SEO,宣告不同語言的頁面
<link rel="alternate" hreflang="x-default" href="https://example.com/" />
<link rel="alternate" hreflang="zh-TW" href="https://example.com/?lang=zh-TW" />
x-default 是 Google 搜尋引擎定義的
<link rel="canonical">canonical = 典範,在這邊代表的是 "preferred URL"
<link rel="canonical" href="https://example.com/" />
<link rel="dns-prefetch">針對 cross-origin domains 先做 dns-prefetch
<link rel="dns-prefetch" href="https://fonts.googleapis.com/" />
<link rel="prefetch">Cache-Control,prefetch 的資源才可以存到 HTTP 快取Sec-Purpose: prefetch, Sec-Fetch-Dest: empty
使用 Node.js http 模組實作:
/、/page2、/page2.js 三個路由import { readFileSync } from "fs";
import http from "http";
import { join } from "path";
const httpServer = http.createServer((req, res) => {
const url = new URL(req.url || "", "http://localhost:5000");
if (url.pathname === "/page2.js") {
res.setHeader("Content-Type", "text/javascript");
res.setHeader("Cache-Control", "public, max-age=60");
res.end("console.log('hello world')");
return;
}
if (url.pathname === "/") {
res.setHeader("Content-Type", "text/html");
res.end(readFileSync(join(import.meta.dirname, "index.html")));
return;
}
if (url.pathname === "/page2") {
res.setHeader("Content-Type", "text/html");
res.end(readFileSync(join(import.meta.dirname, "page2.html")));
return;
}
});
httpServer.listen(5000);
/page2.js
<head>
<link rel="prefetch" href="/page2.js" as="script" />
</head>
<body>
<a href="/page2">Go to page2</a>
</body>
/page2.js
<head>
<xcript src="/page2.js"></xcript>
</head>
/page2
<xcript src="/page2.js"> 吃到 prefetch 的快取
<link rel="preload"><link rel="preload" href="main.js" as="script" />
<link rel="preload" href="font.ttf" as="font" type="font/ttf" crossorigin />
as="font" 的情況,需要明確指定 crossorigin / crossorigin="" / crossorigin="anonymous"
<link rel="modulepreload">Vite 專案會看到,主流的瀏覽器到 2023 年才全數支援
<link rel="modulepreload" href="/assets/chunks/theme.Dk-6AaEp.js" />
跟 <link rel="preload"> 的差異是
The main difference is that preload just downloads the file and stores it in the cache, while modulepreload gets the module, parses and compiles it, and puts the results into the module map so that it is ready to execute.
<link rel="preconnect"><link rel="preconnect" href="https://example.com" />
<link rel="icon">最常用的情境就是 favicon
<link rel="icon" href="favicon.ico" />
<audio crossorigin></audio>
<img crossorigin="" />
<link crossorigin="anonymous" />
<video crossorigin="use-credentials"></video>
<xcript crossorigin="use-credentials"></xcript>
其中,以下三個設定是一樣的意思,都代表 "anonymous"
crossorigin
crossorigin=""
crossorigin="anonymous"
| setting | description |
|---|---|
| crossorigin="anonymous" | crossorigin 請求 "不" 帶 credentials(例如 Cookie) |
| crossorigin="use-credentials" | crossorigin 請求 "會" 帶 credentials(例如 Cookie) |
假設載入第三方套件,需要捕捉詳細錯誤,送到 Error Monitoring Tool,如果沒有 crossorigin 的話,就無法獲得詳細的錯誤資訊
寫個 PoC 來驗證:
import { readFileSync } from "fs";
import http from "http";
import { join } from "path";
const httpServer5000 = http.createServer((req, res) => {
const url = new URL(req.url || "", "http://localhost:5000");
if (url.pathname === "/") {
res.setHeader("Content-Type", "text/html");
res.end(readFileSync(join(import.meta.dirname, "index.html")));
return;
}
});
httpServer5000.listen(5000);
<head>
<xcript> addEventListener("error", console.log); </xcript>
<xcript src="http://localhost:5001/script.js"></xcript>
</head>
import http from "http";
const httpServer5001 = http.createServer((req, res) => {
const url = new URL(req.url || "", "http://localhost:5001");
if (url.pathname === "/script.js") {
res.end("a"); // 刻意觸發 Uncaught ReferenceError: a is not defined
return;
}
});
httpServer5001.listen(5001);
透過 addEventListener 捕捉到的錯誤就不會顯示詳細的錯誤訊息

加上 crossorigin 的話:
<xcript src="http://localhost:5001/script.js" crossorigin></xcript>
res.setHeader("Access-Control-Allow-Origin", "*");
透過 addEventListener 捕捉到的錯誤就會顯示詳細的錯誤訊息

這個章節,我們學到了
<link rel> 的各種用法下一篇會來介紹 HTTP Link